#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import argparse
import logging
import os
import re
import sys
import hashlib
import time

import pygraphviz as pgv
import gvcolor
import filechk

log_format = "%(levelname)s: %(funcName)s@%(filename)s-%(lineno)d: %(message)s"

logging.basicConfig(format=log_format)
log = logging.getLogger("[logdebug]")
log.setLevel(logging.INFO)


class GvRender():

    def __init__(self):
        self.edge_color_min = 10
        self.edge_color_max = 150

        self.node_color_min = 180
        self.node_color_max = 230

        self.cluster_color_min = 230
        self.cluster_color_max = 250

        self.mode = 'tail'

    @classmethod
    def get_str(cls, edge, mode='head'):
        try:
            if mode not in ('head', 'tail', 'headport', 'tailport'):
                return str(time.time())
            left, right = edge[0], edge[1]
            if 'dir' in edge.attr:
                if 'back' == edge.attr['dir']:
                    left, right = right, left

            if mode == 'head':  #  a  -> [b]
                return right
            if mode == 'tail':  # [a] ->  b
                return left

            if edge.attr[mode] and len(edge.attr[mode]) > 0:
                return edge.attr[mode]

            if mode == 'headport':
                return right
            if mode == 'tailport':
                return left

        except:
            return str(time.time())

    @classmethod
    def __render_sg(cls, sgs, clist):
        for sg in sgs:
            bgcolor = gvcolor.random_color(sg.to_string(), clist=clist)
            sg.graph_attr.update(bgcolor=bgcolor)
            cls.__render_sg(sg.subgraphs(), clist)

    def render_node(self, graph:pgv.AGraph, force=False):
        min_gray = self.node_color_min
        max_gray = self.node_color_max

        clist = gvcolor.colors_by_gray(min=min_gray, max=max_gray)
        clist2 = gvcolor.colors_by_gray(min=min_gray - 10, max=max_gray - 10)

        table_pattern = r'table bgcolor=\"[\w ]+\"'
        td_pattern = r'td bgcolor=\"[\w ]+\"'
        tbl_r = re.compile(table_pattern)
        td_r = re.compile(td_pattern)

        for node in graph.nodes():
            color = gvcolor.random_color(node, clist=clist)
            color2 = gvcolor.random_color(node, clist=clist2)
            label = node.attr['label']
            if label and '</table>' in label:   # html
                bgc_tbl = tbl_r.findall(label)
                bgc_td = td_r.findall(label)
                if bgc_tbl:
                    orig = bgc_tbl[0]
                    label = label.replace(orig, 'table bgcolor="{}"'.format(color2), 1)
                else:
                    orig = 'table'
                    label = label.replace(orig, 'table bgcolor="{}"'.format(color2), 1)

                if bgc_td:
                    orig = bgc_td[0]
                    label = label.replace(orig, 'td bgcolor="{}"'.format(color), 1)
                else:
                    orig = 'td'
                    label = label.replace(orig, 'table bgcolor="{}"'.format(color), 1)
                # FIXME: just do at GvCParser
                # node.attr.update(label=label)
            else:
                node.attr.update(style='filled')
                node.attr.update(color=color)

    def render_edge(self, graph:pgv.AGraph, force=False):
        mode = self.mode
        min_gray = self.edge_color_min
        max_gray = self.edge_color_max

        clist = gvcolor.colors_by_gray(min=min_gray, max=max_gray)
        for edge in graph.edges():
            color = gvcolor.random_color(self.get_str(edge, mode), clist=clist)
            edge.attr.update(color=color)

    def render_subgraph(self, graph:pgv.AGraph, force=False):
        min_gray = self.cluster_color_min
        max_gray = self.cluster_color_max

        clist = gvcolor.colors_by_gray(min=min_gray, max=max_gray)
        self.__render_sg(graph.subgraphs(), clist)

    def render_graph(self, graph:pgv.AGraph, force=False):
        comments = []
        comments.append('mode: {}'.format(self.mode))
        comments.append('node: {}~{}'.format(self.node_color_min, self.node_color_max))
        comments.append('edge: {}~{}'.format(self.edge_color_min, self.edge_color_max))
        comments.append('subgraph: {}~{}'.format(self.cluster_color_min, self.cluster_color_max))

        graph.graph_attr['comment'] = ', '.join(comments)

    def render_all(self, graph:pgv.AGraph, force=False):
        self.render_node(graph, force)
        self.render_edge(graph, force)
        self.render_subgraph(graph, force)
        self.render_graph(graph, force)

def get_render(args):
    render = GvRender()

    render.node_color_min = args.na
    render.node_color_max = args.nb

    render.edge_color_min = args.ea
    render.edge_color_max = args.eb

    render.cluster_color_min = args.sa
    render.cluster_color_max = args.sb

    render.mode = args.mode

    return render

def gv_render(render, filename, render_gv, layout):
    graph = pgv.AGraph(filename, strict=False)

    comment = os.path.basename(sys.argv[0])
    comment += ' ' + ' '.join(sys.argv[1:])
    graph.graph_attr['comment'] = comment

    render.render_all(graph)

    render_png = render_gv.replace('.gv', '.png')
    if not render_gv.endswith('.gv'):
        render_png = render_gv + '.png'
    graph.write(render_gv)
    graph.draw(render_png, prog=layout)

def main(args):
    layout = args.layout
    filename = args.file
    render_gv = args.output
    if not render_gv:
        if filename.endswith('.gv'):
            render_gv = filename.replace('.gv', '_render.gv')
        else:
            render_gv = filename + '_render.gv'

    render = get_render(args)

    print("output to {}(.png)".format(render_gv))
    gv_render(render, filename, render_gv, layout)

    if args.deamon:
        gv_fchk = filechk.FileCheck(filename)
        while True:
            try:
                if gv_fchk.changed():
                    print("{}: output to {}(.png)".format(gv_fchk.count_changed, render_gv))
                    gv_render(render, filename, render_gv, layout)
            except Exception as e:
                print(e)
            time.sleep(0.5)

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.description = '%(prog)s: Graphviz file converter'

    parser.add_argument("-d", "--deamon", action='store_true', help="deamon thread")

    parser.add_argument("-m", "--mode", required=True, help="head/tail/headport/tailport")
    parser.add_argument("-l", "--layout", default='dot', help="layout engine, default dot")

    parser.add_argument("--na", type=int, default=150, help="node color min")
    parser.add_argument("--nb", type=int, default=230, help="node color max")

    parser.add_argument("--ea", type=int, default=10, help="edge color min")
    parser.add_argument("--eb", type=int, default=130, help="edge color max")

    parser.add_argument("--sa", type=int, default=235, help="subgraph bgcolor min")
    parser.add_argument("--sb", type=int, default=250, help="subgraph bgcolor max")

    parser.add_argument("-o", "--output", help="output file, default same as input")
    parser.add_argument("file", type=str, help="input file")

    args = parser.parse_args()
    print(args)
    main(args)
